#![feature(async_await)]

mod stage;

mod error;

mod opt;

mod render;

use crate::render::*;
use crate::error::*;
use crate::opt::Opt;
use crate::stage::*;
use async_std::task;
use async_std::sync::Mutex;
use async_std::task::block_on;
use lazy_static::lazy_static;
use slog::o;
use slog::Drain;
use slog::FnValue;
use slog::Logger;
use slog::Record;
use slog_term::{FullFormat, PlainSyncDecorator};
use std::fs::{create_dir, OpenOptions};
use std::path::PathBuf;
use structopt::StructOpt;
use error_chain::bail;

lazy_static! {
    pub static ref LOGGER: Logger = {
        let log_path = "compare.log";
        let file = OpenOptions::new()
            .create(true)
            .write(true)
            .truncate(true)
            .open(log_path)
            .unwrap();
        let decorator = PlainSyncDecorator::new(file);
        fn module_and_line(record: &Record) -> String {
            format!("{}:{}", record.module(), record.line())
        }
        Logger::root(
            FullFormat::new(decorator).build().fuse(),
            o!("module" => FnValue(module_and_line)),
        )
    };
    pub static ref OPT: Opt = { Opt::from_args() };
    pub static ref STAT: Mutex<Stats> = {
        Mutex::new(Stats {
            WAP: vec![],
            AC: 0,
            WA: 0,
            UN: 0,
        })
    };
}

fn main() -> Result<()> {
    OPT.init();
    block_on(stage1())?;
    block_on(stage2())?;
    Ok(())
}

async fn stage1() -> Result<()> {
    if OPT.is_check() {
        compile(OPT.get_program_path(), "program.exe").await?;
    } else {
        let mut futures = vec![];
        futures.push(task::spawn(compile(OPT.get_data_cpp_path(), "data.exe")));
        futures.push(task::spawn(compile(
            OPT.get_original_path(),
            "original.exe",
        )));
        futures.push(task::spawn(compile(
            OPT.get_optimized_path(),
            "optimized.exe",
        )));
        for f in futures {
            f.await?;
        }
    }
    Ok(())
}

async fn stage2() -> Result<()> {
    let data_path = OPT.get_data_path();
    let points_count = OPT.get_points_count();
    if !data_path.is_dir() {
        create_dir(&data_path)?;
    }
    let mut handlers = vec![];
    for i in 1..=points_count {
        handlers.push(stage2_impl(i, &data_path));
    }
    for h in handlers {
        h.await?;
    }
    Ok(())
}

async fn stage2_impl(index: usize, data_path: &PathBuf) -> Result<()> {
    if OPT.is_check() {
        let data = read_data(&data_path, index).await?;
        match spawn_and_save("program.exe", &data_path, data.into_bytes(), None, index).await {
            Ok(_) => {
            },
            Err(e) => {
                STAT.lock().await.UN += 1;
                bail!(e);
            }
        };
        let expected = read_answer(&data_path, index).await?;
        let actual = read_output(&data_path, None, index).await?;
        match compare_output(expected, actual).await {
            Err(_) => {
                STAT.lock().await.WAP.push(index);
            },
            _ => {},
        };
    } else {
        let data = generate_data(&data_path, index).await?;
        match spawn_and_save("original.exe", &data_path, data.clone(), Some("_r"), index).await {
            Ok(_) => {
            },
            Err(e) => {
                STAT.lock().await.UN += 1;
                bail!(e);
            }
        };
        match spawn_and_save("optimized.exe", &data_path, data, Some("_p"), index).await {
            Ok(_) => {
            },
            Err(e) => {
                STAT.lock().await.UN += 1;
                bail!(e);
            },
        };
        let expected = read_output(&data_path, Some("_r"),index).await?;
        let actual = read_output(&data_path, Some("_p"), index).await?;
        match compare_output(expected, actual).await {
            Err(_) => {
                STAT.lock().await.WAP.push(index);
            },
            _ => {},
        };
    }
    on_change().await;
    Ok(())
}
